/*	Script written by Yoni Couriel
	ycouriel@gmail.com             

	Usage Example:
	<div>
	<pre formatcs=1 types="MyStaticClass">
	class MyNotStaticClass
	{
		object x = MyStaticClass.x;
	}
	</pre>
	</div>
	<pre formatcs=1>int x;</pre>
	<script type="text/javascript" src="csharp_formatter_normal.js"></script>
	<script type="text/javascript">
	document.write(findAndFormatCSharp() + " elements were formatted");
	</script>

	Note:
	The <pre> is inside a <div> because it shouldn't be modified by the script
	while having unclosed parent. In most cases the <div> is not necessary
	because they usually don't have the same parent

	I took the definitions of keywords and preprocessors from:
	http://www.manoli.net/csharpformat/

	And the style of the <pre> element from:
	http://codeformatter.blogspot.com/
*/

if (!window.CSHARPFORMATTER) (function() {
	CSHARPFORMATTER = {

		template : "<div class='csharpcode'>Code:<pre style='border:1px dashed #CCCCCC;overflow-x:auto;overflow-y:hidden;background:#f0f0f0;padding:0px;color:#000000;text-align:left;line-height20px;color:#000000;word-wrap:normal;'>#CODE#</pre></div>",
	
		style :	"<style type=\"text/css\">.csharpcode, .csharpcode pre\n" +
				"{\n" +
				"	font-size: 13.3333px;\n" +
				"	font-width: 400;\n" +
				"	color: black;\n" +
				"	font-family: \"Courier New\";\n" +
				"}\n" +
				".csharpcode pre { margin: 0px; }\n" +
				".csharpcode .comment { color: #008000; }\n" +
				".csharpcode .comment2 { color: #808080; }\n" +
				".csharpcode .type { color: #2B91AF; }\n" +
				".csharpcode .keyword { color: #0000FF; }\n" +
				".csharpcode .string { color: #A31515; }\n" +
				".csharpcode .preproc { color: #0000FF; }\n" +
				"</style>\n",

		keywords :
			"abstract|as|base|bool|break|byte|case|catch|char|"
			+ "checked|class|const|continue|decimal|default|delegate|do|double|else|"
			+ "enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|"
			+ "if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|"
			+ "object|operator|out|override|partial|params|private|protected|public|readonly|"
			+ "ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|"
			+ "switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|"
			+ "using|value|virtual|void|volatile|where|while|yield|get|set",
			
		preprocessors :
			"#if|#else|#elif|#endif|#define|#undef|#warning|"
			+ "#error|#line|#region|#endregion|#pragma",

		// extended list of commonly used types
		types :	"Action|AppDomain|Application|Array|Attribute|Boolean|Byte|Char|Console|Convert|DateTime|Delegate|Double|Enum|Environment|EventArgs|EventHandler|Exception|Flags|Func|GC|ICloneable|IComparable|IConvertible|IDisposable|IFormatProvider|IndexOutOfRangeException|Int32|Int64|IntPtr|InvalidOperationException|MarshalByRefObject|Math|NonSerialized|Nullable|Object|Obsolete|OutOfMemoryException|Random|SByte|Serializable|Single|STAThread|String|StringComparison|StringSplitOptions|TimeSpan|TimeZone|TimeZoneInfo|Type|UInt32|UInt64|UIntPtr|UnauthorizedAccessException|Uri|Version|Dictionary|HashSet|ICollection|IDictionary|IEnumerable|IList|List|Queue|SortedDictionary|Stack|Assembly|BindingFlags|FieldInfo|MemberInfo|ParameterInfo|PropertyInfo|ChannelServices|RemotingConfiguration|TcpChannel|Clipboard|DragDropEffects|DragEventArgs|DragDropKeyStates|MessageBox|DialogResult|Point|CookieContainer|CredentialCache|HttpWebRequest|HttpWebResponse|WebRequest|WebResponse|IXmlSerializable|XmlIgnore|XmlEnum|XmlInclude",

		formatCSharpCode : function (code, usermoretypes)
		{
			var storage = [];
			var moretypes = "";
			var generictypes = "";

			if (usermoretypes) {
				var m = usermoretypes.match(/([\w0-9]+)/);
				for (i = 0; i < m.length; ++i) {
					identifiedNewType(m[i]);
				}
			}
			
			var iskeywordregexp = new RegExp("^(" + self.keywords + ")$");
			
			var namespaceptr = "(?:\\w+\\.)*\\w+";
			
			var nameptr = "[A-Za-z_]\\w*";
			
			// this regular expression is more accurate, but unfortunately
			// in both firefox and internet explorer it takes a lot
			// of time (5 minutes) to work with it
			//var typeptr = "(?:" + namespaceptr + "\\.|)" + nameptr +"(?:(?:<|&lt;)\\s*(?:\\s*(?:<|&lt;)\\s*|" + nameptr + "(?:\\s*,\\s*|)|\\s*(?:>|&gt;)\\s*,\\s*)*\\s*(?:\\s*(?:>|&gt;))+|)(?:\\[\\s*\\]|)(?!\\w)";
			// this is the best alternative that works and that i could think of:
			var typeptr = "(?:" + namespaceptr + "\\.|)" + nameptr + "\\s*(?:<[\\w<>\\s,]*>|)(?:\\[\\s*\\]|)(?!\\w)";

			var formatted = code;
			
			//formatted = formatted.replace(new RegExp(typeptr, "gm"), function(all){return "["+all+"]";}); return formatSpecialChars(formatted);
			
			// handle comments and strings
			formatted = formatted.replace(/@"(?:[^"]+|"")*"|"(?:[^\\"]+|\\.)*"|'(?:[^\\']+|\\.)*'|\/\/.*|\/\*[\s\S]*?\*\//g, formatAndStoreStringOrComment);
			formatted = formatted;
			// handle preprocessors
			formatted = formatted.replace(new RegExp("(" + self.preprocessors + ")(.*|$)", "gm"), formatAndStorePreprocessor);
			// handle attributes before classes
			formatted = formatted.replace(/(\[)(\w[\w0-9]*)(?=[^\{\}]*class)/gm, formatAndStoreAttribute);
			// handle class, enum, interface, struct declarations
			formatted = formatted.replace(/(?:^|\b)((?:class|enum|interface|struct)\s+)([\s\S]*?)(\s*\{)/gm, parseFormatAndStoreClassDeclaration);
			// handle case: "typeof(MyClass)"
			formatted = formatted.replace(/(typeof\s*\(\s*)(.*?)(\s*\))/gm, parseFormatAndStoreComplexType);
			// handle case: "as MyClass;", "new List<MyClass>("
			formatted = formatted.replace(new RegExp('((?:as|new)\\s+)(' + typeptr + ')(\\W)', 'gm'), parseFormatAndStoreComplexType);
			// handle cases: "public List<T> func()", "private MyClass x;"
			// type space name terminator
			// cannot be merged with "as MyClass" above because type/keyword are reversed
			formatted = formatted.replace(new RegExp('(' + typeptr + ')(\\s+)([\\w\\.]+)(?=\\s*[^\\w\\s<$])', 'gm'), function(all,type,space,m){
				// for cases like: "in Assembly.Get()) {"
				if (type == "in") return all;
				
				var k1 = isKeyword(type);
				var k2 = isKeyword(m);
				if (k1 || k2) {
					return store((k1?formatType("keyword", type):type) + space + (k2?formatType("keyword",m):m));
				}
				identifiedNewComplexType(type);
				// storing all to support "Form1 Form1;"
				return store(formatComplexType(type) + space + m);
			});
			// handle cases: "for (IList l in ll)"
			// terminator type space name
			formatted = formatted.replace(new RegExp('((?:^|[^\\w\\s$])[\\s]*)(' + typeptr + ')(\\s+)([\\w\\.]+)', 'gm'), function(all,before,type,space,m){
				var k1 = isKeyword(type);
				var k2 = isKeyword(m);
				if (k1 || k2) {
					return before + store((k1?formatType("keyword", type):type) + space + (k2?formatType("keyword",m):m));
				}
				identifiedNewComplexType(type);
				// storing all to support "Form1 Form1;"
				return before + store(formatComplexType(type) + space + m);
			});
			// handle keywords
			formatted = formatted.replace(new RegExp("\\b(" + self.keywords + ")\\b", "g"), function(all){return store(formatType("keyword",all));});

			formatted = formatSpecialChars(formatted);

			// handle types
			formatted = formatted.replace(new RegExp("\\b(" + combineOrRegexps(self.types, moretypes) + ")\\b", "g"), "<span class='type'>$1</span>");    
			
			formatted = restoreAll(formatted);
			return formatted;

			// handles: "class Form1<T,R> : Form, where T : Dictionary<NewClassType, int>, R"
			function parseFormatAndStoreClassDeclaration(all, before, declaration, after)
			{
				var m = declaration.match(/^\s*(\w+)\s*(?:<(.+?)>|)([\s\S]*)/);
				var className = m[1];
				var types = m[2];
				var tail = m[3];

				if (className) {
					identifiedNewType(className);
				}
				if (types) {
					var m2 = types.match(new RegExp(nameptr, "g"));
					if (m2) {
						var i;
						for (i = 0; i < m2.length; ++i) {
							identifiedNewGenericType(m2[i]);
						}
					}
				}
				if (tail) {
					var m3 = tail.match(new RegExp(nameptr, "g"));
					if (m3) {
						var j;
						for (j = 0; j < m3.length; ++j) {
							identifiedNewType(m3[j]);
						}
					}
				}
				var formatted = formatComplexType(declaration);
				return before + store(formatted) + after;
			}
			
			// handles: "new Dictionary<List<int>, string>", "as T)", 
			function parseFormatAndStoreComplexType(all, before, type, after)
			{
				identifiedNewComplexType(type);
				return before + store(formatComplexType(type)) + after;
			}

			function isKeyword(text)
			{
				return text.match(iskeywordregexp);
			}
			
			// formats something like: "Generics.List<Dictionary<int,string>>[]"
			function formatComplexType(type)
			{
				var split = type.match("(?:(" + namespaceptr + ")\\.|)([\\s\\S]*)");
				var namespace = split[1];
				var tail = split[2];
				//return (namespace?namespace:"") + tail;
				return  (namespace?namespace:"") + tail.replace(/(\w+)([\W]*)/gm, function(all,m,after) {
					var r;
					if (isKeyword(m)) {
						r = formatType("keyword", m);
					} else if (m.match(new RegExp("(^|\\b)(" + moretypes + ")(\\b|$)"))) {
						r = formatType("type", m);
					} else {
						r = m;
					}
					return r + formatSpecialChars(after);
				});
			}

			// example: Generics.Dictionary<List<int>, MyClass>
			function identifiedNewComplexType(name)
			{
				var split = name.match("(?:(" + namespaceptr + ")\\.|)(.*)");
				var namespace = split[1];
				var tail = split[2];
				var m = tail.match(/([\w\.]+)/g);
				if (m) {
					for (var i = 0; i < m.length; ++i) {
						identifiedNewType(m[i]);
					}
				}
			}
			
			// example: MyNamespace.MyClass
			function identifiedNewType(name)
			{
				var split = name.match("(" + namespaceptr + "\\.|)(.*)");
				var tail = split[2];
				if (tail.match("^(" + combineOrRegexps(combineOrRegexps(generictypes, self.keywords), "lt|gt") + ")$")) {
					return;
				}
				moretypes = combineOrRegexps(moretypes, tail);
			}
			
			// example: T
			function identifiedNewGenericType(name)
			{
				generictypes = combineOrRegexps(generictypes, name);
			}

			function restoreAll(text)
			{
				return text.replace(/\$(\d+)\$/g, restore);
			}

			function restore(all, num)
			{
				return storage[num];
			}
			
			function combineOrRegexps(regex1, regex2)
			{
				if (regex1 == "") {
					return regex2;
				} else {
					if (regex2 != "") {
						return regex1 + "|" + regex2;
					} else {
						return regex1;
					}
				}
			}
			
			function formatAndStoreStringOrComment(text)
			{
				if (text.match(/^[@"']/)) {
					return formatAndStoreString(text);
				} else {
					return formatAndStoreComment(text);
				}
			}
			
			function formatAndStoreString(text)
			{
				return store(formatType("string", text));
			}
			
			function formatAndStorePreprocessor(all, pre, after)
			{
				return store(formatType("preproc", pre) + after);
			}
			
			function formatAndStoreAttribute(all, before, att)
			{
				return store(before + formatType("type", att));
			}

			function formatAndStoreComment(text)
			{
				var formatted = "";
				var m = text.match(/^\/\/\/(?!\/)(.*)/);
				if (m) {
					var tail = m[1];
					if (tail.match(/<.*>/)) {
						formatted = tail.replace(/([^<]*)(<.*?>)([^<]*)/g, function(all,c1,c2,c3) {
							return formatType("comment", c1) + formatType("comment2", c2) + formatType("comment", c3);
						});
					} else {
						formatted = formatType("comment", tail);
					}
					formatted = formatType("comment2", "///") +  formatted;
				} else {
					formatted = formatType("comment", text);
				}
				return store(formatted);
			}
			
			function store(m)
			{
				var i = storage.length;
				storage[i] = m;
				return "$" + i + "$";
			}

			function formatSpecialChars(strIn)
			{
				if (!strIn || strIn == "")
					return strIn;
				
				/*return strIn
					.replace(/&/gm, "&amp;")
					.replace(/</gm, "&lt;")
					.replace(/>/gm, "&gt;")
					.replace(/"/gm, "&quot;");
					*/
					
				// i copied this function from
				// http://formatmysourcecode.blogspot.com/
				var strOut = "";
				for (var i = 0; i < strIn.length; i++ ) {
					var code = strIn.charCodeAt(i);
					switch (code) {
						case 34:
							strOut += "&quot;";
							break;
						case 38:
							strOut += "&amp;";
							break;
						case 60:
							strOut += "&lt;";
							break;
						case 62:
							strOut += "&gt;";
							break;
						case 124: // vertical pipe (blogger modifies this)
							strOut += "&#124;";
							break;
						default:
							if ( code >= 32 && code <= 127 ) {
								strOut += strIn.charAt(i);
							} else {
								strOut += "&#" + code + ";";
							}
							break;
					}
				}
				return strOut;
			}

			function formatType(type, text)
			{
				return "<span class='" + type + "'>" + formatSpecialChars(text) + "</span>";
			}
		},

		init : function()
		{
			if (!initialized) {
				initialized = true;
				document.write(self.style);
				findAndFormatCSharp = self.findAndFormatCSharp;
			}
		},
		
		findAndFormatCSharp : function()
		{
			function restoreSpecialChars(html)
			{
				if (!html || html == "")
					return html;
				
				return html
					.replace(/&quot;/gm, " ")
					.replace(/&amp;/gm, "&")
					.replace(/&lt;/gm, "<")
					.replace(/&gt;/gm, ">")
					.replace(/&#(\d+);/gm, function(all,code){
						return String.fromCharCode(code);
						});
			}

			var e = document.getElementsByTagName("pre");
			var count = 0;
			for (var i = 0; i < e.length; i++) {
				var lmnt = e[i];
				if (lmnt.getAttribute("formatcs") == 1 || lmnt.getAttribute("formatcsharp") == 1) {
					var parent = lmnt.parentNode;
					var wrapper = document.createElement("div");
					var code = restoreSpecialChars(lmnt.innerHTML);
					wrapper.innerHTML = self.template.replace("#CODE#", self.formatCSharpCode(code, lmnt.getAttribute("types")));
					parent.replaceChild(wrapper, lmnt);
					if (navigator.appName == "Microsoft Internet Explorer") {
						var insidepres = wrapper.getElementsByTagName("pre");
						for (var j = 0; j < insidepres.length; j++) {
							var pre2fix = insidepres[j];
							if (pre2fix.scrollWidth > pre2fix.offsetWidth) {
								pre2fix.style['paddingBottom'] = '20px';
							}
						}
					}
					count++;
				}
			}
			
			return count;
		}
	};
	var initialized = false;
	var self = CSHARPFORMATTER;
	self.init();
})();
